require([
  'jquery',
  'common/breeze/callbackpass',
  'common/breeze/gamepadcontroller',
  'common/geometry/geometry2d',
  'common/geometry/geometry3d',
  'common/breeze/inputconstants',
  'common/breeze/keyboardconstants',
  'common/breeze/keyboardcontroller',
  'common/breeze/loop',
  'common/breeze/terrainphysicspass',
  'common/breeze/threerenderpass',
  'lib/domReady',
  'lib/Three'],
function(
  $,
  CallbackPass,
  GamepadController,
  geometry2d,
  geometry3d,
  inputconstants,
  keyboardconstants,
  KeyboardController,
  Loop,
  TerrainPhysicsPass,
  ThreeRenderPass,
  domReady,
  THREE) {

function initScene(scene, camera) {
  camera.position.set(0, 0, 120);
  camera.lookAt(scene.position);

  var light = new THREE.PointLight(0xFFFFFF);
  light.position.set(10, 0, 10);
  scene.add(light);
};

function addTerrain(scene, terrainPhysicsWorld, polyLine) {
  terrainPhysicsWorld.addTerrain(polyLine);

  var material = new THREE.LineBasicMaterial({
    color: new THREE.Color(0x000000),
    opacity: 1,
    linewidth: 1
  });
  var geometry = new THREE.Geometry();
  for (var i = 0; i < polyLine.points.length; i++) {
    var point = polyLine.points[i];
    geometry.vertices.push(new THREE.Vector3(
        point.x,
        point.y,
        0));
  }
  scene.add(new THREE.Line(geometry, material));
};

function GameObject(
    logicPass, postPhysicsPass, scene, controller, terrainPhysicsWorld) {
  window.gameObject = this;

  this.controller_ = controller;
  this.material_ = new THREE.MeshLambertMaterial( { color: 0xFFFF00 } );

  this.mesh_ = new THREE.Mesh(
    // new THREE.CubeGeometry(5, 5, 5),
    new THREE.CylinderGeometry(
      0,  // Top radius
      2.5,  // Bottom radius
      5,  // Height
      8,  // SegmentsRadius
      1,  // segmentsHeight
      false),  // openEnded
    this.material_);
  this.mesh_.useQuaternion = true;
  this.mesh_.quaternion = new THREE.Quaternion(0, 0, 0, 1);
  scene.add(this.mesh_);

  this.terrainPhysicsWorld_ = terrainPhysicsWorld;
  this.terrainObject_ = this.terrainPhysicsWorld_.addObject(
    geometry2d.zeroVector(),  // Starting position
    1,  // Mass
    1.5,  // Friction
    geometry2d.zeroVector());  // Velocity

  // Set initial position of mesh from physics simulation.
  this.onPostPhysicsPass_(0);

  this.logicPassKey_ =
    logicPass.addCallback(this.onLogicPass_.bind(this));

  this.postPhysicsPassKey_ =
    postPhysicsPass.addCallback(this.onPostPhysicsPass_.bind(this));
};

GameObject.prototype.onLogicPass_ = function(timeDeltaMillis) {
  this.terrainObject_.applyForce(new geometry2d.Vector(0, -10));

  var moveVector = this.controller_.getVector(Input.MOVE);

  this.terrainObject_.applyForceAlongTerrain(moveVector.scale(10));

  this.material_.color = new THREE.Color(
    this.controller_.buttonDown(Input.COLOR) ?
      0xFF0000 : 0x0000FF);

  if (this.controller_.lastFrameEventCount(
      Input.JUMP, inputconstants.ButtonEvent.PRESS) > 0) {
    // TODO: jump
  }
};

GameObject.prototype.onPostPhysicsPass_ = function(timeDeltaMillis) {
  this.mesh_.position.x = this.terrainObject_.postUpdatePosition.x;
  this.mesh_.position.y = this.terrainObject_.postUpdatePosition.y;

  // this.mesh_.quaternion.x = rotation.x();
  // this.mesh_.quaternion.y = rotation.y();
  // this.mesh_.quaternion.z = rotation.z();
  // this.mesh_.quaternion.w = rotation.w();
};

Input = {
  MOVE: 0,
  JUMP: 1,
  COLOR: 2
};

domReady(function() {
  var loop = new Loop();

  // Add render pass first to ensure that one frame is rendered in the
  // initial state.
  var threeRenderPass = new ThreeRenderPass(400, 300);
  $('body').append(threeRenderPass.domElement);
  initScene(threeRenderPass.scene, threeRenderPass.camera);
  loop.addPass(threeRenderPass);

  var logicPass = new CallbackPass();
  loop.addPass(logicPass);

  var resetInputPass = new CallbackPass();
  loop.addPass(resetInputPass);

  var terrainPhysicsPass = new TerrainPhysicsPass();
  loop.addPass(terrainPhysicsPass);

  var postPhysicsPass = new CallbackPass();
  loop.addPass(postPhysicsPass);

  ////////////////////////////////////////////////////////////////////////
  // Set up world
  ////////////////////////////////////////////////////////////////////////

  addTerrain(
    threeRenderPass.scene,
    terrainPhysicsPass.terrainPhysicsWorld,
    new geometry2d.PolyLine(
      new geometry2d.Vector(-20, 30),
      new geometry2d.Vector(-10, 0),
      new geometry2d.Vector(0, -5),
      new geometry2d.Vector(10, 0),
      new geometry2d.Vector(20, 30)));

  ////////////////////////////////////////////////////////////////////////
  // Set up controller and player
  ////////////////////////////////////////////////////////////////////////

  KeyboardController.init(resetInputPass);

  var controller = new KeyboardController();
  controller.mapVector(
    Input.MOVE,
    keyboardconstants.Keycode.W,
    keyboardconstants.Keycode.D,
    keyboardconstants.Keycode.S,
    keyboardconstants.Keycode.A);
  controller.mapButton(
    Input.JUMP, keyboardconstants.Keycode.SPACE);
  controller.mapButton(
    Input.COLOR, keyboardconstants.Keycode.B);

/*
  var controller = new GamepadController(resetInputPass);
  controller.mapVector(Input.MOVE, 0, 1);
  controller.mapButton(Input.JUMP, 2);
  controller.mapButton(Input.COLOR, 3);
*/

  var gameObject = new GameObject(
    logicPass,
    postPhysicsPass,
    threeRenderPass.scene,
    controller,
    terrainPhysicsPass.terrainPhysicsWorld);

  loop.start();
});

});  // require
